package crp.core.resource.app.approval;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import crp.core.course.domain.models.CourseFacultyEntity;
import crp.core.course.domain.repositories.CourseFacultyRepository;
import crp.core.faculty.domain.models.FacultyEntity;
import crp.core.resource.domain.events.ResourcePublicized;
import crp.core.resource.domain.models.CourseResourceEntity;
import crp.core.resource.domain.models.ResourceWorkflowContextUtil;
import crp.core.resource.domain.repositories.CourseResourceRepository;
import crp.core.resource.domain.services.ResourceEditor;
import crp.core.shared.PublishStatus;
import com.qwlabs.cdi.Dispatchable;
import crp.core.shared.utils.Times;
import crp.core.workflow.domain.events.WorkflowInstanceEnded;
import crp.core.workflow.domain.events.WorkflowInstanceStarted;
import crp.core.workflow.domain.events.WorkflowTaskEnded;
import crp.core.workflow.domain.models.*;
import crp.core.workflow.domain.repositories.WorkflowInstanceRepository;
import crp.core.workflow.domain.services.WorkflowEventHandler;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Event;
import javax.inject.Inject;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@ApplicationScoped
public class ResourceApprovalEventHandler implements Dispatchable<String>, WorkflowEventHandler {
    private final static String APPROVAL_OPERATION = "approvalOperation";
    private final Event<ResourcePublicized> resourcePublicizedEvent;
    private final ResourceEditor resourceEditor;
    private final Times times;
    private final CourseResourceRepository courseResourceRepository;
    private final CourseFacultyRepository courseFacultyRepository;
    private final WorkflowInstanceRepository instanceRepository;

    @Inject
    public ResourceApprovalEventHandler(Event<ResourcePublicized> resourcePublicizedEvent,
                                        ResourceEditor resourceEditor,
                                        Times times, CourseResourceRepository courseResourceRepository,
                                        CourseFacultyRepository courseFacultyRepository,
                                        WorkflowInstanceRepository instanceRepository) {
        this.resourcePublicizedEvent = resourcePublicizedEvent;
        this.resourceEditor = resourceEditor;
        this.times = times;
        this.courseResourceRepository = courseResourceRepository;
        this.courseFacultyRepository = courseFacultyRepository;
        this.instanceRepository = instanceRepository;
    }

    @Override
    public void onEvent(WorkflowInstanceStarted event) {
        CourseResourceEntity courseResource = courseResourceRepository
                .findByResourceIds(Lists.newArrayList(event.getContext().getExternalId())).findFirst().get();
        List<FacultyEntity> faculty =
                courseFacultyRepository.findByCourseIds(Sets.newHashSet(courseResource.getCourse().getId()), null)
                        .map(CourseFacultyEntity::getFaculty).collect(Collectors.toList());
        resourceEditor.update(event.getContext().getExternalId(), resource -> {
            event.getContext().appendFormAttributes(Objects.requireNonNull(ResourceWorkflowContextUtil
                    .buildInstanceContext(courseResource, faculty)));
            resource.setApproval(Approval.builder()
                    .submittedAt(times.nowInstant())
                    .submittedBy(event.getContext().getSubmittedBy())
                    .externalId(event.getInstanceId())
                    .status(ApprovalStatus.PENDING)
                    .build());
        });
    }

    @Override
    public void onEvent(WorkflowInstanceEnded event) {
        WorkflowInstanceEntity instance = instanceRepository.findById(event.getInstanceId());
        instance.setStatus(WorkflowInstanceStatus.COMPETED);

        CourseResourceEntity courseResource = courseResourceRepository
                .findByResourceIds(Lists.newArrayList(event.getContext().getExternalId())).findFirst().get();
        if (ApprovalOperation.PASSED.name().equals(event.getContext().getFormAttributes().get(APPROVAL_OPERATION))) {
            courseResource.getResource().getApproval().setStatus(ApprovalStatus.PASSED);
            courseResource.getResource().setPublishStatus(PublishStatus.PUBLISHED);
            resourcePublicizedEvent.fire(ResourcePublicized.builder()
                    .resourceId(courseResource.getResource().getId())
                    .build());
        } else {
            courseResource.getResource().getApproval().setStatus(ApprovalStatus.REJECTED);
        }
    }

    @Override
    public void onEvent(WorkflowTaskEnded event) {
        event.getInstance().getContext().appendFormAttributes(APPROVAL_OPERATION, event.getTask().getOperation().name());
        instanceRepository.persist(event.getInstance());
    }

    @Override
    public boolean dispatchable(String context) {
        return ResourceApproval.TOPIC.equals(context);
    }
}
